/*!	 colormatrix.h
**	 Matrix definitions for 4D affine transformations of color channels
**
**	......... ... 2016 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifndef __SYNFIG_COLORMATRIX_H
#define __SYNFIG_COLORMATRIX_H

#include <cassert>
#include <cmath>

#include <ETL/stringf>

#include <synfig/angle.h>
#include <synfig/real.h>
#include <synfig/string.h>

#include "color.h"

namespace synfig
{

/*!	\class ColorMatrix
**	TODO:  writeme (Matrix 5x5, actually used sub-rectangle 5x4)
*/
class ColorMatrix
{
public:
    typedef Color::value_type value_type;
    typedef value_type value_row[5];
    typedef value_type value_array[25];
    typedef value_row value_matrix[5];

    typedef value_type(*transform_func_ptr)(const ColorMatrix &m, const Color &src);
    typedef void (*batch_func_ptr)(const ColorMatrix &m, value_type *dest, const Color *src, const Color *src_end);

    class BatchProcessor;

    // The matrix array
    union {
        value_matrix m;
        value_array c;
        struct {
            value_type m00, m01, m02, m03, m04;
            value_type m10, m11, m12, m13, m14;
            value_type m20, m21, m22, m23, m24;
            value_type m30, m31, m32, m33, m34;
            value_type m40, m41, m42, m43, m44;
        };
    };

    // Default constructor makes an identity matrix
    ColorMatrix():
        m00(1.0), m01(0.0), m02(0.0), m03(0.0), m04(0.0),
        m10(0.0), m11(1.0), m12(0.0), m13(0.0), m14(0.0),
        m20(0.0), m21(0.0), m22(1.0), m23(0.0), m24(0.0),
        m30(0.0), m31(0.0), m32(0.0), m33(1.0), m34(0.0),
        m40(0.0), m41(0.0), m42(0.0), m43(0.0), m44(1.0) { }

    ColorMatrix(
        value_type m00, value_type m01, value_type m02, value_type m03, value_type m04,
        value_type m10, value_type m11, value_type m12, value_type m13, value_type m14,
        value_type m20, value_type m21, value_type m22, value_type m23, value_type m24,
        value_type m30, value_type m31, value_type m32, value_type m33, value_type m34,
        value_type m40, value_type m41, value_type m42, value_type m43, value_type m44
    ):
        m00(m00), m01(m01), m02(m02), m03(m03), m04(m04),
        m10(m10), m11(m11), m12(m12), m13(m13), m14(m14),
        m20(m20), m21(m21), m22(m22), m23(m23), m24(m24),
        m30(m30), m31(m31), m32(m32), m33(m33), m34(m34),
        m40(m40), m41(m41), m42(m42), m43(m43), m44(m44)
    { }

    ColorMatrix(Color axis_r, Color axis_g, Color axis_b, Color axis_a, Color offset):
        m00(axis_r.get_r()), m01(axis_r.get_g()), m02(axis_r.get_b()), m03(axis_r.get_a()), m04(0.0),
        m10(axis_g.get_r()), m11(axis_g.get_g()), m12(axis_g.get_b()), m13(axis_g.get_a()), m14(0.0),
        m20(axis_b.get_r()), m21(axis_b.get_g()), m22(axis_b.get_b()), m23(axis_b.get_a()), m24(0.0),
        m30(axis_a.get_r()), m31(axis_a.get_g()), m32(axis_a.get_b()), m33(axis_a.get_a()), m34(0.0),
        m40(offset.get_r()), m41(offset.get_g()), m42(offset.get_b()), m43(offset.get_a()), m44(1.0)
    { }

    Color get_axis_r()const
    {
        return Color(m00, m01, m02, m03);
    }
    Color get_axis_g()const
    {
        return Color(m10, m11, m12, m13);
    }
    Color get_axis_b()const
    {
        return Color(m20, m21, m22, m23);
    }
    Color get_axis_a()const
    {
        return Color(m30, m31, m32, m33);
    }
    Color get_offset()const
    {
        return Color(m40, m41, m42, m43);
    }
    Color get_constant()const
    {
        return get_offset();
    }

    // set_identity member. Set an identity matrix
    ColorMatrix& set_identity()
    {
        return *this = ColorMatrix();
    }

    bool is_identity() const
    {
        return *this == ColorMatrix();
    }

    bool is_zero() const;
    bool is_zero(int channel) const;

    bool is_constant() const;
    bool is_constant(int channel) const;

    bool is_copy() const;
    bool is_copy(int channel) const;

    bool is_transparent() const
    {
        return is_zero(3);
    }
    bool is_affects_transparent() const;

    ColorMatrix& set_scale(value_type r, value_type g, value_type b, value_type a = value_type(1.0));
    ColorMatrix& set_scale_rgba(value_type rgba)
    {
        return set_scale(rgba, rgba, rgba, rgba);
    }
    ColorMatrix& set_scale_rgb(value_type rgb)
    {
        return set_scale(rgb, rgb, rgb);
    }
    ColorMatrix& set_scale(const Color &c)
    {
        return set_scale(c.get_r(), c.get_g(), c.get_b(), c.get_a());
    }

    ColorMatrix& set_translate(value_type r, value_type g, value_type b, value_type a = value_type(0.0));
    ColorMatrix& set_translate(const Color &c)
    {
        return set_translate(c.get_r(), c.get_g(), c.get_b(), c.get_a());
    }

    ColorMatrix& set_encode_yuv();
    ColorMatrix& set_decode_yuv();
    ColorMatrix& set_rotate_uv(const Angle &a);

    ColorMatrix& set_constant(const Color &c);
    ColorMatrix& set_replace_color(const Color &c);
    ColorMatrix& set_replace_alpha(value_type x);
    ColorMatrix& set_brightness(value_type x);
    ColorMatrix& set_contrast(value_type x);
    ColorMatrix& set_exposure(value_type x);
    ColorMatrix& set_hue_saturation(const Angle &hue, value_type saturation);
    ColorMatrix& set_hue(const Angle &x)
    {
        return set_hue_saturation(x, 1.0);
    }
    ColorMatrix& set_saturation(value_type x)
    {
        return set_hue_saturation(Angle::deg(0.0), x);
    }
    ColorMatrix& set_invert_color();
    ColorMatrix& set_invert_alpha();

    Color get_transformed(Color color) const;

    value_row& operator[](int r)
    {
        return m[r];
    }
    const value_row& operator[](int r) const
    {
        return m[r];
    }

    bool operator==(const ColorMatrix &rhs) const;
    bool operator!=(const ColorMatrix &rhs) const
    {
        return !(*this == rhs);
    }

    // operator*=. Multiplication and assignment of one matrix by another
    // @param rhs the right hand side of the multiplication operation
    // @return the modified resulting matrix
    ColorMatrix operator*=(const ColorMatrix &rhs);

    // operator*=. Multiplication and assignment of one matrix by a scalar
    // @param rhs the number to multiply by
    // @return the modifed resulting matrix
    ColorMatrix operator*=(const value_type &rhs);

    // operator*. Multiplication of one matrix by another
    // @param rhs the right hand side of the multiplication operation
    // @return the resulting matrix
    ColorMatrix operator*(const ColorMatrix &rhs)const
    {
        return ColorMatrix(*this) *= rhs;
    }

    // operator*. Multiplication of one matrix by a number
    // @param rhs the number to multiply by
    // @return the resulting matrix
    ColorMatrix operator*(const value_type &rhs)const
    {
        return ColorMatrix(*this) *= rhs;
    }

    // Get the string of the ColorMatrix
    // @return String type. A string representation of the matrix
    // components.
    String get_string(int spaces = 0, String before = String(), String after = String())const;
};

class ColorMatrix::BatchProcessor
{
private:
    ColorMatrix matrix;

    bool zero_all;
    union {
        bool zero[4];
        struct {
            bool zero_r, zero_g, zero_b, zero_a;
        };
    };

    Color constant_value;
    bool constant_all;
    union {
        bool constant[4];
        struct {
            bool constant_r, constant_g, constant_b, constant_a;
        };
    };

    bool copy_all;
    union {
        bool copy[4];
        struct {
            bool copy_r, copy_g, copy_b, copy_a;
        };
    };

    bool affects_transparent;

    union {
        transform_func_ptr transform_funcs[4];
        struct {
            transform_func_ptr transform_func_r, transform_func_g, transform_func_b, transform_func_a;
        };
    };

    union {
        batch_func_ptr batch_funcs[4];
        struct {
            batch_func_ptr batch_func_r, batch_func_g, batch_func_b, batch_func_a;
        };
    };

public:
    BatchProcessor(const ColorMatrix &matrix = ColorMatrix());

    bool is_zero() const
    {
        return zero_all;
    }
    bool is_constant() const
    {
        return constant_all;
    }
    bool is_copy() const
    {
        return copy_all;
    }
    bool is_affects_transparent() const
    {
        return affects_transparent;
    }

    const Color& get_constant_value() const
    {
        return constant_value;
    }

    const ColorMatrix& get_matrix() const
    {
        return matrix;
    }
    void process(Color *dest, int dest_stride, const Color *src, int src_stride, int width, int height) const;
};

}; // END of namespace synfig

#endif